<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Frame;

use Exception;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use Srv\Apps\Proto\Enum\ErrCodeEnum;
use Srv\Libs\Boot;
use Srv\Libs\Common\CommArray;
use Srv\Libs\Common\CommFile;
use Srv\Libs\Common\CommFrom;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommValid;
use Srv\Libs\ConfModel\ResponseConf;
use Srv\Libs\ConfModel\RouteConf;
use Srv\Libs\ConfModel\RouteConfData;
use Srv\Libs\Tools\Http;

final class Route
{
    private static ?ResponseConf $ResponseConf      = null;                 // ResponseConf
    private static array $classNameList             = [];                   // Class列表
    private static array $urlListGroupWebs          = [];                   // URL列表 Webs
    private static array $urlListGroupSocks         = [];                   // URL列表 Socks
    private static array $regListGroupWebs          = [];                   // REG列表 Webs
    private static array $regListGroupSocks         = [];                   // REG列表 Socks

    /**
     * @param ResponseConf $ResponseConf
     * @return void
     * @throws Exception
     * init
     */
    public static function init(ResponseConf $ResponseConf):void
    {
        self::$ResponseConf     = $ResponseConf;
        $RouteConfList          = Conf::getRouteConfGroup()->getGroupNameList();
        if(count($RouteConfList) > 0) foreach ($RouteConfList as $RouteConf){
            if(!($RouteConf instanceof RouteConf)) continue;
            $groupName          = $RouteConf->getGroupName();
            $urlRegListData     = [];
            $urlListData        = self::loadActions(self::getAtPathActions($RouteConf->getActionPaths(), $RouteConf->getActions()), $urlRegListData);
            if($RouteConf->isWebSocket()){
                self::$urlListGroupSocks[$groupName]    = $urlListData;
                self::$regListGroupSocks[$groupName]    = $urlRegListData;
            }else{
                self::$urlListGroupWebs[$groupName]     = $urlListData;
                self::$regListGroupWebs[$groupName]     = $urlRegListData;
            }
        }
    }

    /**
     * @param array $actionPathList
     * @param array $actionList
     * @return array
     * getAtPathActions
     */
    private static function getAtPathActions(array $actionPathList, array $actionList):array
    {
        $actionClassNameList    = [];
        $actionPathListSize     = count($actionPathList);
        $actionListSize         = count($actionList);
        if($actionPathListSize < 1 && $actionListSize < 1) return $actionClassNameList;
        if($actionPathListSize > 0) foreach($actionPathList as $actionPath){
            $fileList               = [];
            if(CommFile::getFileList($actionPath, $fileList) && count($fileList) > 0) foreach($fileList as $filePath){
                $actionClassName    = Boot::getFilePathClassName($filePath);
                if((strlen($actionClassName) > 3 && substr($actionClassName, -3) === 'Act') || (strlen($actionClassName) > 6 && substr($actionClassName, -6) === 'Action')) $actionClassNameList[] = $actionClassName;
            }
        }
        if($actionListSize > 0) foreach($actionList as $actionClassName) $actionClassNameList[] = $actionClassName;
        $actionClassNameList    = array_unique($actionClassNameList);
        sort($actionClassNameList);
        return $actionClassNameList;
    }

    /**
     * @param array $actionClassNameList
     * @param array $urlRegListData
     * @return array
     * @throws Exception
     * loadActions
     */
    private static function loadActions(array $actionClassNameList, array &$urlRegListData):array
    {
        $urlListData                            = [];
        $urlRegListData                         = [];
        if(count($actionClassNameList) < 1) return $urlListData;
        foreach ($actionClassNameList as $actionClassName){
            if(!class_exists($actionClassName)) throw new Exception('Action ClassName Not Found, ['.$actionClassName.']');
            if(!((new $actionClassName()) instanceof ActionAbstract)) throw new Exception('Action ClassName Not Instanceof ActionAbstract, ['.$actionClassName.']');
            $ReqClassNameReflectionClass        = new ReflectionClass($actionClassName);
            $ReflectionMethodList               = $ReqClassNameReflectionClass->getMethods(ReflectionMethod::IS_PUBLIC);
            if(count($ReflectionMethodList) < 1) continue;
            foreach ($ReflectionMethodList as $ReflectionMethod){
                if(!($ReflectionMethod instanceof ReflectionMethod) || $ReflectionMethod->isStatic() || !$ReflectionMethod->isPublic()) continue;
                if(!preg_match_all('/@(auth|code|url|req|rsp|flag|err|limit)[ \t]*([\S ]*)\n/i', $ReflectionMethod->getDocComment(), $matchList)) continue;
                $flagList               = [];
                $urlList                = [];
                $authStr                = '';
                $codeStr                = '';
                $DTOClassName           = '';
                $VOClassName            = '';
                $DTOCheckConf           = [];
                $ErrVOClassNameList     = [];
                $limitList              = [];
                foreach ($matchList[1] as $matchIndex => $matchVal){
                    $valStr             = trim($matchList[2][$matchIndex]??'');
                    $matchKey           = strtolower($matchVal);
                    switch($matchKey){
                        case 'flag':{
                            $flagVal    = $valStr;
                            if(strlen($flagVal) > 0) foreach (explode(',', $flagVal) as $flagValItem){
                                $flagValItem = trim($flagValItem);
                                if(strlen($flagValItem) > 0) $flagList[] = $flagValItem;
                            }
                            break;
                        }
                        case 'url':{
                            $urlPath    = $valStr;
                            if(strlen($urlPath) > 0) $urlList[]  = $urlPath;
                            break;
                        }
                        case 'auth':{
                            $authStr    = $valStr;
                            break;
                        }
                        case 'code':{
                            $codeList           = [];
                            if(strlen($valStr) > 0) foreach(explode(',', $valStr) as $codeName){
                                if(!ErrCodeEnum::nameExists($codeName)) throw new Exception('ErrCodeName ['.$codeName.'] Not Found, ['.$actionClassName.']');
                                $codeList[]     = ErrCodeEnum::getValue($codeName);
                            }
                            if(count($codeList) > 0){
                                $codeList       = array_unique($codeList);
                                $codeStr        = implode(',', $codeList);
                            }
                            break;
                        }
                        case 'req':{
                            if(strlen($valStr) > 0){
                                $valStr         = preg_replace('/\./', '\\', $valStr);
                                if(!class_exists($valStr)) throw new Exception($matchKey.' ClassName Not Found, ['.$actionClassName.'->'.$ReflectionMethod->getName().']['.$valStr.']');
                                $optionalNameList       = [];
                                $checkConfList          = [];
                                $optionalAll            = true;
                                self::reqParseDTO('', $valStr, $actionClassName, $ReflectionMethod->getName(), $DTOClassName, $optionalNameList, $checkConfList, $optionalAll);
                                if(count($checkConfList) > 0) $DTOCheckConf = [$optionalNameList, $checkConfList, $optionalAll];
                            }
                            break;
                        }
                        case 'rsp':{
                            if(strlen($valStr) > 0){
                                $valStr                 = preg_replace('/\./', '\\', $valStr);
                                if(!class_exists($valStr)) throw new Exception($matchKey.' ClassName Not Found, ['.$actionClassName.'->'.$ReflectionMethod->getName().']['.$valStr.']');
                                $valStrObject           = new $valStr();
                                if(!($valStrObject instanceof VOAbstract)) throw new Exception($matchKey.' ClassName Not Instanceof VOAbstract, ['.$actionClassName.'->'.$ReflectionMethod->getName().']');
                                $VOClassName            = get_class($valStrObject);
                            }
                            break;
                        }
                        case 'err':{
                            if(strlen($valStr) > 0){
                                $valStr                 = preg_replace('/\./', '\\', $valStr);
                                if(!class_exists($valStr)) throw new Exception($matchKey.' ClassName Not Found, ['.$actionClassName.'->'.$ReflectionMethod->getName().']['.$valStr.']');
                                $valStrObject           = new $valStr();
                                if(!($valStrObject instanceof VOAbstract)) throw new Exception($matchKey.' ClassName Not Instanceof VOAbstract, ['.$actionClassName.'->'.$ReflectionMethod->getName().']');
                                $ErrVOClassNameList[]   = get_class($valStrObject);
                            }
                            break;
                        }
                        case 'limit':{
                            if(strlen($valStr) > 0){
                                if(preg_match('/^([A-Z_\d]+)(\([^)]*\))?(\$?):(.*)/', $valStr, $matchLimit)){
                                    $limitConf          = [];
                                    if(CheckLimit::parseConf($matchLimit[1], $matchLimit[2], $matchLimit[3], $matchLimit[4], $valStr, $limitConf)){
                                        $limitList[]    = $limitConf;
                                    }else{
                                        throw new Exception($matchKey.' Limit ParseConf Error, ['.$actionClassName.'->'.$ReflectionMethod->getName().'], '.$valStr);
                                    }
                                }else{
                                    throw new Exception($matchKey.' Limit Conf Error, ['.$actionClassName.'->'.$ReflectionMethod->getName().'], '.$valStr);
                                }
                            }
                            break;
                        }
                    }
                }
                if(in_array('skip', $flagList, true) || in_array('SKIP', $flagList, true)) continue;    // 跳过
                $urlList                        = array_unique($urlList);
                if(count($urlList) > 0){
                    if(in_array($actionClassName, self::$classNameList, true)){
                        $indexNum               = CommArray::getIndexNum(self::$classNameList, $actionClassName);
                    }else{
                        $indexNum               = count(self::$classNameList);
                        self::$classNameList[]  = $actionClassName;
                    }
                    foreach ($urlList as $urlPath){
                        $urlListData[$urlPath]  = [$indexNum, $ReflectionMethod->getName(), $authStr, $codeStr, $DTOClassName, $VOClassName, $DTOCheckConf, $flagList, $ErrVOClassNameList, $limitList];
                        if(preg_match_all('/<\w+\??>/', preg_replace(['/\[:(\w+)]/', '/:(\w+)/'], ['<\1?>', '<\1>'], $urlPath), $matchVar)){
                            $urlPathVarList     = [];
                            foreach($matchVar[0] as $varName){
                                $optional       = false;
                                if(strpos($varName, '?') !== false){
                                    $varName    = substr($varName, 1, -2);
                                    $optional   = true;
                                }else{
                                    $varName    = substr($varName, 1, -1);
                                }
                                $urlPathVarList[]   = [$varName, null, $optional];
                            }
                            $urlPathRex         = '/^'.preg_replace(['/\//', '/\\\\\/\[:(\w+)]/', '/:(\w+)/', '/<\w+\??>/', '/<\w+>/'], ['\\/', '(\/\w+)?', '(\w+)', '(\w+)?', '(\w+)'], $urlPath).'$/';
                            $urlRegListData[]   = [$urlPathRex, $urlPathVarList, $urlPath];
                        }
                    }
                }
            }
        }
        return $urlListData;
    }

    /**
     * @param string $propertyNamePrefix
     * @param string $classNameDTO
     * @param string $actionClassName
     * @param string $actionMethodName
     * @param string $DTOClassName
     * @param array $optionalNameList
     * @param array $checkConfList
     * @param bool $optionalAll
     * @return void
     * @throws Exception
     * reqParseDTO
     */
    private static function reqParseDTO(string $propertyNamePrefix, string $classNameDTO, string $actionClassName, string $actionMethodName, string &$DTOClassName, array &$optionalNameList, array &$checkConfList, bool &$optionalAll):void
    {
        $classNameDTOObject                 = new $classNameDTO();
        if(!($classNameDTOObject instanceof DTOAbstract)) throw new Exception('ClassName Not Instanceof DTOAbstract, ['.$actionClassName.'->'.$actionMethodName.']');
        $DTOClassName                       = get_class($classNameDTOObject);
        // 解析参数配置
        $DTOClassNameReflectionClass        = new ReflectionClass($classNameDTOObject);
        $DTOReflectionPropertyList          = $DTOClassNameReflectionClass->getProperties(ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PRIVATE);
        if(count($DTOReflectionPropertyList) > 0) foreach ($DTOReflectionPropertyList as $DTOReflectionProperty){
            $propertyName                   = $DTOReflectionProperty->getName();
            $propertyDocComment             = $DTOReflectionProperty->getDocComment();
            if(is_string($propertyDocComment) && preg_match('#\*(.*)\s+.*\s+.*<code>(\S+\s+)?(\S+)\s+\S+\s+=\s+(\d+);</code>#', $propertyDocComment, $match)){
                $commentStr                 = preg_replace('"&#64;"', '@', $match[1]);
                $commentOnly                = trim(strval(preg_replace('/@?(\[[^]]+])/', '', $commentStr)));
                $errMsgSet                  = '';
                $optional                   = false;
                $commentCheckList           = [];
                if(preg_match_all('/\s+@(\[[^]]+])/', $commentStr, $matchDtoList)){
                    foreach ($matchDtoList[1] as $matchItem){
                        $matchItem          = trim($matchItem);
                        if(strlen($matchItem) < 1) continue;
                        if(in_array(strtolower($matchItem), ['[optional]', '["optional"]', '[\'optional\']'], true)){
                            $optional       = true;
                            continue;
                        }
                        if(preg_match('/\[["\']?ERR:\s?(\S+)["\']?]/i', $matchItem, $matchErrMsg)){
                            $errMsgSet      = trim($matchErrMsg[1]);
                            continue;
                        }
                        $commentCheckList[] = $matchItem;
                    }
                }
                $type                       = str_replace('.', '\\', trim($match[3], '.'));
                $typeClassName              = '';
                if(strpos($type, '\\') !== false){
                    if(!CommValid::isExistsClass($type)) throw new Exception('CheckDTO FAIL, Class Not Found ['.$type.'], ['.$actionClassName.'->'.$actionMethodName.']['.$propertyNamePrefix.$propertyName.']');
                    $typeClassName          = $type;
                }
                $typeNumUnsigned            = in_array($type, ['uint32', 'uint64'], true);
                $typeNum32                  = in_array($type, ['int32', 'uint32', 'sint32', 'fixed32', 'sfixed32'], true);
                if($typeNum32){
                    $defNumMax              = pow(2, 32-($typeNumUnsigned?0:1));
                    $defNumMin              = $typeNumUnsigned ? 0 : $defNumMax*-1;
                }else{
                    $defNumMax              = PHP_INT_MAX;
                    $defNumMin              = $typeNumUnsigned ? 0 : PHP_INT_MIN;
                }
                if(count($commentCheckList) > 0) foreach ($commentCheckList as $commentVal){
                    if(substr($commentVal, 0, 1) !== '[' || substr($commentVal, -1) !== ']') continue;
                    $checkConf              = CommJson::decodeArray($commentVal);
                    if(count($checkConf) < 1) throw new Exception('CheckDTO FAIL, ['.$actionClassName.'->'.$actionMethodName.']['.$propertyNamePrefix.$propertyName.']'.$commentVal);
                    $checkType              = trim($checkConf[0]??'');
                    $checkErrMsg            = 'CheckDTO FAIL, ['.$actionClassName.'->'.$actionMethodName.']['.$propertyNamePrefix.$propertyName.'-'.$checkType.']';
                    // 子类参数
                    $subDTOClassName        = '';
                    $subOptionalNameList    = [];
                    $subCheckConfList       = [];
                    $subOptionalAll         = true;
                    switch ($checkType){
                        case CheckDTO::CHECK_TYPE_NUMBER:
                        case CheckDTO::CHECK_TYPE_NUMBER_STR_LEN:
                        case CheckDTO::CHECK_TYPE_NUMBER_STR_INT:
                        case CheckDTO::CHECK_TYPE_STRING:
                        case CheckDTO::CHECK_TYPE_ARRAY:{
                            if($checkConf[1] === 'MAX') $checkConf[1] = $defNumMax;
                            if($checkConf[1] === 'MIN') $checkConf[1] = $defNumMin;
                            if($checkConf[2] === 'MAX') $checkConf[2] = $defNumMax;
                            if($checkConf[2] === 'MIN') $checkConf[2] = $defNumMin;
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !is_int($checkConf[1]) || !is_int($checkConf[2])) throw new Exception($checkErrMsg);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_FLOAT:{
                            if($checkConf[1] === 'MAX') $checkConf[1] = PHP_FLOAT_MAX;
                            if($checkConf[1] === 'MIN') $checkConf[1] = $defNumMin;
                            if($checkConf[2] === 'MAX') $checkConf[2] = PHP_FLOAT_MAX;
                            if($checkConf[2] === 'MIN') $checkConf[2] = $defNumMin;
                            if($checkConf[3] === 'MAX') $checkConf[3] = CheckDTO::FLOAT_DOT_MAX_NUM;
                            if($checkConf[3] === 'MIN') $checkConf[3] = CheckDTO::FLOAT_DOT_MIN_NUM;
                            if($checkConf[4] === 'MAX') $checkConf[4] = CheckDTO::FLOAT_DOT_MAX_NUM;
                            if($checkConf[4] === 'MIN') $checkConf[4] = CheckDTO::FLOAT_DOT_MIN_NUM;
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !isset($checkConf[3]) || !isset($checkConf[4]) || !is_float($checkConf[1]) || !is_float($checkConf[2]) || !is_int($checkConf[3]) || !is_int($checkConf[4])) throw new Exception($checkErrMsg);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_FLOAT_STR:{
                            if($checkConf[3] === 'MAX') $checkConf[3] = CheckDTO::FLOAT_DOT_MAX_NUM;
                            if($checkConf[3] === 'MIN') $checkConf[3] = CheckDTO::FLOAT_DOT_MIN_NUM;
                            if($checkConf[4] === 'MAX') $checkConf[4] = CheckDTO::FLOAT_DOT_MAX_NUM;
                            if($checkConf[4] === 'MIN') $checkConf[4] = CheckDTO::FLOAT_DOT_MIN_NUM;
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !isset($checkConf[3]) || !isset($checkConf[4]) || !is_string($checkConf[1]) || !is_string($checkConf[2]) || !is_int($checkConf[3]) || !is_int($checkConf[4])) throw new Exception($checkErrMsg);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ENUM:{
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !is_array($checkConf[1]) || !is_bool($checkConf[2])) throw new Exception($checkErrMsg);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_BOOL:{
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !is_bool($checkConf[1]) || !is_bool($checkConf[2])) throw new Exception($checkErrMsg);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_NUM:
                        case CheckDTO::CHECK_TYPE_ARRAY_STR:{
                            if($checkConf[1] === 'MAX') $checkConf[1] = $defNumMax;
                            if($checkConf[1] === 'MIN') $checkConf[1] = $defNumMin;
                            if($checkConf[2] === 'MAX') $checkConf[2] = $defNumMax;
                            if($checkConf[2] === 'MIN') $checkConf[2] = $defNumMin;
                            if($checkConf[3] === 'MAX') $checkConf[3] = $defNumMax;
                            if($checkConf[3] === 'MIN') $checkConf[3] = $defNumMin;
                            if($checkConf[4] === 'MAX') $checkConf[4] = $defNumMax;
                            if($checkConf[4] === 'MIN') $checkConf[4] = $defNumMin;
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !isset($checkConf[3]) || !isset($checkConf[4]) || !is_int($checkConf[1]) || !is_int($checkConf[2]) || !is_int($checkConf[3]) || !is_int($checkConf[4])) throw new Exception($checkErrMsg);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_FLOAT:{
                            if($checkConf[1] === 'MAX') $checkConf[1] = $defNumMax;
                            if($checkConf[1] === 'MIN') $checkConf[1] = $defNumMin;
                            if($checkConf[2] === 'MAX') $checkConf[2] = $defNumMax;
                            if($checkConf[2] === 'MIN') $checkConf[2] = $defNumMin;
                            if($checkConf[3] === 'MAX') $checkConf[3] = PHP_FLOAT_MAX;
                            if($checkConf[3] === 'MIN') $checkConf[3] = PHP_FLOAT_MIN;
                            if($checkConf[4] === 'MAX') $checkConf[4] = PHP_FLOAT_MAX;
                            if($checkConf[4] === 'MIN') $checkConf[4] = PHP_FLOAT_MIN;
                            if($checkConf[5] === 'MAX') $checkConf[5] = CheckDTO::FLOAT_DOT_MAX_NUM;
                            if($checkConf[5] === 'MIN') $checkConf[5] = CheckDTO::FLOAT_DOT_MIN_NUM;
                            if($checkConf[6] === 'MAX') $checkConf[6] = CheckDTO::FLOAT_DOT_MAX_NUM;
                            if($checkConf[6] === 'MIN') $checkConf[6] = CheckDTO::FLOAT_DOT_MIN_NUM;
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !isset($checkConf[3]) || !isset($checkConf[4]) || !isset($checkConf[5]) || !isset($checkConf[6]) || !is_int($checkConf[1]) || !is_int($checkConf[2]) || !is_float($checkConf[3]) || !is_float($checkConf[4]) || !is_int($checkConf[5]) || !is_int($checkConf[6])) throw new Exception($checkErrMsg);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_FLOAT_STR:{
                            if($checkConf[1] === 'MAX') $checkConf[1] = $defNumMax;
                            if($checkConf[1] === 'MIN') $checkConf[1] = $defNumMin;
                            if($checkConf[2] === 'MAX') $checkConf[2] = $defNumMax;
                            if($checkConf[2] === 'MIN') $checkConf[2] = $defNumMin;
                            if($checkConf[5] === 'MAX') $checkConf[5] = CheckDTO::FLOAT_DOT_MAX_NUM;
                            if($checkConf[5] === 'MIN') $checkConf[5] = CheckDTO::FLOAT_DOT_MIN_NUM;
                            if($checkConf[6] === 'MAX') $checkConf[6] = CheckDTO::FLOAT_DOT_MAX_NUM;
                            if($checkConf[6] === 'MIN') $checkConf[6] = CheckDTO::FLOAT_DOT_MIN_NUM;
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !isset($checkConf[3]) || !isset($checkConf[4]) || !isset($checkConf[5]) || !isset($checkConf[6]) || !is_int($checkConf[1]) || !is_int($checkConf[2]) || !is_string($checkConf[3]) || !is_string($checkConf[4]) || !is_int($checkConf[5]) || !is_int($checkConf[6])) throw new Exception($checkErrMsg);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_ENUM:{
                            if($checkConf[1] === 'MAX') $checkConf[1] = $defNumMax;
                            if($checkConf[1] === 'MIN') $checkConf[1] = $defNumMin;
                            if($checkConf[2] === 'MAX') $checkConf[2] = $defNumMax;
                            if($checkConf[2] === 'MIN') $checkConf[2] = $defNumMin;
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !isset($checkConf[3]) || !isset($checkConf[4]) || !is_int($checkConf[1]) || !is_int($checkConf[2]) || !is_array($checkConf[3]) || !is_bool($checkConf[4])) throw new Exception($checkErrMsg);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_ARRAY_BOOL:{
                            if($checkConf[1] === 'MAX') $checkConf[1] = $defNumMax;
                            if($checkConf[1] === 'MIN') $checkConf[1] = $defNumMin;
                            if($checkConf[2] === 'MAX') $checkConf[2] = $defNumMax;
                            if($checkConf[2] === 'MIN') $checkConf[2] = $defNumMin;
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !isset($checkConf[3]) || !isset($checkConf[4]) || !is_int($checkConf[1]) || !is_int($checkConf[2]) || !is_bool($checkConf[3]) || !is_bool($checkConf[4])) throw new Exception($checkErrMsg);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_CLASS_ENUM:{
                            if(strlen($typeClassName) < 1 || isset($checkConf[2]) || (isset($checkConf[1]) && !is_array($checkConf[1]))) throw new Exception($checkErrMsg);
                            $typeClassNameObject    = new $typeClassName();
                            if(!($typeClassNameObject instanceof EnumAbstract)) throw new Exception($checkErrMsg);
                            if(!isset($checkConf[1])) $checkConf[1] = [];
                            $checkConf[2]           = $checkConf[1];
                            $checkConf[1]           = $typeClassName;
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_CLASS_ARRAY_ENUM:{
                            if(strlen($typeClassName) < 1 || isset($checkConf[4]) || (isset($checkConf[3]) && !is_array($checkConf[3]))) throw new Exception($checkErrMsg);
                            if($checkConf[1] === 'MAX') $checkConf[1] = $defNumMax;
                            if($checkConf[1] === 'MIN') $checkConf[1] = $defNumMin;
                            if($checkConf[2] === 'MAX') $checkConf[2] = $defNumMax;
                            if($checkConf[2] === 'MIN') $checkConf[2] = $defNumMin;
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !is_int($checkConf[1]) || !is_int($checkConf[2])) throw new Exception($checkErrMsg);
                            $typeClassNameObject    = new $typeClassName();
                            if(!($typeClassNameObject instanceof EnumAbstract)) throw new Exception($checkErrMsg);
                            if(!isset($checkConf[3])) $checkConf[3] = [];
                            $checkConf[4]           = $checkConf[3];
                            $checkConf[3]           = $typeClassName;
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_CLASS_MESSAGE:{
                            if(strlen($typeClassName) < 1 || isset($checkConf[2]) || (isset($checkConf[1]) && !is_array($checkConf[1]))) throw new Exception($checkErrMsg);
                            $typeClassNameObject    = new $typeClassName();
                            if(!($typeClassNameObject instanceof DTOAbstract)) throw new Exception($checkErrMsg);
                            if(!isset($checkConf[1])) $checkConf[1] = [];
                            $checkConf[2]           = $checkConf[1];
                            $checkConf[1]           = $typeClassName;
                            self::reqParseDTO($propertyNamePrefix.$propertyName.'.', $typeClassName, $actionClassName, $actionMethodName, $subDTOClassName, $subOptionalNameList, $subCheckConfList, $subOptionalAll);
                            break;
                        }
                        case CheckDTO::CHECK_TYPE_CLASS_ARRAY_MESSAGE:{
                            if(strlen($typeClassName) < 1 || isset($checkConf[4]) || (isset($checkConf[3]) && !is_array($checkConf[3]))) throw new Exception($checkErrMsg);
                            if($checkConf[1] === 'MAX') $checkConf[1] = $defNumMax;
                            if($checkConf[1] === 'MIN') $checkConf[1] = $defNumMin;
                            if($checkConf[2] === 'MAX') $checkConf[2] = $defNumMax;
                            if($checkConf[2] === 'MIN') $checkConf[2] = $defNumMin;
                            if(!isset($checkConf[1]) || !isset($checkConf[2]) || !is_int($checkConf[1]) || !is_int($checkConf[2])) throw new Exception($checkErrMsg);
                            $typeClassNameObject    = new $typeClassName();
                            if(!($typeClassNameObject instanceof DTOAbstract)) throw new Exception($checkErrMsg);
                            if(!isset($checkConf[3])) $checkConf[3] = [];
                            $checkConf[4]           = $checkConf[3];
                            $checkConf[3]           = $typeClassName;
                            self::reqParseDTO($propertyNamePrefix.$propertyName.'.*.', $typeClassName, $actionClassName, $actionMethodName, $subDTOClassName, $subOptionalNameList, $subCheckConfList, $subOptionalAll);
                            break;
                        }
                        default:{
                            if(isset($checkConf[1]) && !is_array($checkConf[1])) throw new Exception($checkErrMsg);
                            $checkConf[0]           = CheckDTO::getValidNameId($checkType);
                            if($checkConf[0] < 1) throw new Exception($checkErrMsg);
                        }
                    }
                    $checkConfList[]                = ['name' => $propertyNamePrefix.$propertyName, 'check' => $checkConf, 'comment' => $commentOnly, 'errMsgSet' => $errMsgSet];
                    if(count($subCheckConfList) > 0){   // 子类
                        $checkConfList              = array_merge_recursive($checkConfList, $subCheckConfList);
                        if(count($subOptionalNameList) > 0) $optionalNameList = array_merge_recursive($optionalNameList, $subOptionalNameList);
                    }
                }
                if($optional){
                    $optionalNameList[]             = $propertyNamePrefix.$propertyName;
                }else{
                    $optionalAll                    = false;
                }
            }
        }
    }

    /**
     * @return string
     * getGroupNameSocksDefault
     */
    public static function getGroupNameSocksDefault():string
    {
        return self::$ResponseConf->getGroupNameSocksDefault();
    }

    /**
     * @return string
     * getGroupNameWebsDefault
     */
    public static function getGroupNameWebsDefault():string
    {
        return self::$ResponseConf->getGroupNameWebsDefault();
    }

    /**
     * @return string
     * getUrlPath
     */
    public static function getUrlPath():string
    {
        $urlPath            = '';
        $actionName         = self::$ResponseConf->getActionName();
        $actionOrder        = self::$ResponseConf->getActionOrder();
        $actionRootNum      = max(self::$ResponseConf->getActionRootNum(), 0);
        if(strlen($actionName) > 0 && strlen($actionOrder) > 0) $urlPath = preg_replace('/[\/\\\\]+/', '/', CommFrom::getStringOrder($actionName, $actionOrder));
        if(strlen($urlPath) < 1) $urlPath = preg_replace('/[\/\\\\]+/', '/', Http::getUrlRequest());
        $urlPathLength      = strlen($urlPath);
        if($urlPathLength > 0 && $urlPath !== '/'){
            if($actionRootNum > 0){
                $urlPathTmpList = explode('/', ltrim($urlPath, '/'));
                $rootPath       = implode('/', array_slice($urlPathTmpList, 0, $actionRootNum));
                $urlPath        = '/'.implode('/', array_slice($urlPathTmpList, $actionRootNum));
                EnvData::setRootPath($rootPath);
            }
            if($urlPathLength > 2 && strpos($urlPath, '%') !== false) $urlPath = urldecode($urlPath);
        }else{
            $urlPath        = self::$ResponseConf->getActionDefault();
        }
        return $urlPath;
    }

    /**
     * @param string $groupName
     * @param string $urlPath
     * @return RouteConfData|null
     * getWebsPathConf
     */
    public static function getWebsPathConf(string $groupName, string $urlPath):?RouteConfData
    {
        if(strlen($urlPath) < 1 || strlen($groupName) < 1 || !isset(self::$urlListGroupWebs[$groupName])) return null;
        if(!isset(self::$urlListGroupWebs[$groupName][$urlPath])){
            foreach (self::$regListGroupWebs[$groupName] as $urlRegData){
                if(preg_match($urlRegData[0], $urlPath, $matchVar) !== 1) continue;
                $urlRegVarList  = $urlRegData[1]??[];
                $urlPathReg     = $urlRegData[2]??'';
                foreach ($matchVar as $matchIndex => $matchVal){
                    if($matchIndex < 1 || !isset($urlRegVarList[$matchIndex-1])) continue;
                    $urlRegVarList[$matchIndex-1][1] = $urlRegVarList[$matchIndex-1][2] ? ltrim($matchVal, '/') : $matchVal;
                }
                $urlConfData    = self::$urlListGroupWebs[$groupName][$urlPathReg];
                if(!isset(self::$classNameList[$urlConfData[0]])) return null;
                $urlConfData[0] = self::$classNameList[$urlConfData[0]];
                return new RouteConfData($urlConfData, $groupName, $urlPath, false, $urlRegVarList);
            }
            return null;
        }
        $urlConfData    = self::$urlListGroupWebs[$groupName][$urlPath];
        if(!isset(self::$classNameList[$urlConfData[0]])) return null;
        $urlConfData[0] = self::$classNameList[$urlConfData[0]];
        return new RouteConfData($urlConfData, $groupName, $urlPath, false, []);
    }

    /**
     * @param string $groupName
     * @param string $urlPath
     * @return RouteConfData|null
     * getSocksPathConf
     */
    public static function getSocksPathConf(string $groupName, string $urlPath):?RouteConfData
    {
        if(strlen($urlPath) < 1 || strlen($groupName) < 1 || !isset(self::$urlListGroupSocks[$groupName])) return null;
        if(!isset(self::$urlListGroupSocks[$groupName][$urlPath])){
            foreach (self::$regListGroupSocks[$groupName] as $urlRegData){
                if(preg_match($urlRegData[0], $urlPath, $matchVar) !== 1) continue;
                $urlRegVarList  = $urlRegData[1]??[];
                $urlPathReg     = $urlRegData[2]??'';
                foreach ($matchVar as $matchIndex => $matchVal){
                    if($matchIndex < 1 || !isset($urlRegVarList[$matchIndex-1])) continue;
                    $urlRegVarList[$matchIndex-1][1] = $urlRegVarList[$matchIndex-1][2] ? ltrim($matchVal, '/') : $matchVal;
                }
                $urlConfData    = self::$urlListGroupSocks[$groupName][$urlPathReg];
                if(!isset(self::$classNameList[$urlConfData[0]])) return null;
                $urlConfData[0] = self::$classNameList[$urlConfData[0]];
                return new RouteConfData($urlConfData, $groupName, $urlPath, false, $urlRegVarList);
            }
            return null;
        }
        $urlConfData    = self::$urlListGroupSocks[$groupName][$urlPath];
        if(!isset(self::$classNameList[$urlConfData[0]])) return null;
        $urlConfData[0] = self::$classNameList[$urlConfData[0]];
        return new RouteConfData($urlConfData, $groupName, $urlPath, false, []);
    }

    /**
     * @param string $groupName
     * @param string $urlPath
     * @return string
     * getWebsPathAuthKey
     */
    public static function getWebsPathAuthKey(string $groupName, string $urlPath):string
    {
        if(strlen($groupName) < 1 || !isset(self::$urlListGroupWebs[$groupName])) return '';
        if(strlen($urlPath) < 1 || !isset(self::$urlListGroupWebs[$groupName][$urlPath])) return '';
        $urlConfData    = self::$urlListGroupWebs[$groupName][$urlPath];
        return $urlConfData[2]??'';
    }

    /**
     * @return array
     * initData
     */
    public static function initData():array
    {
        $urlListGroupSocks      = [];
        $urlListGroupWebs       = [];
        $RouteConfList          = Conf::getRouteConfGroup()->getGroupNameList();
        if(count($RouteConfList) > 0) foreach ($RouteConfList as $RouteConf){
            if(!($RouteConf instanceof RouteConf)) continue;
            $groupName          = $RouteConf->getGroupName();
            try{
                $urlListData    = self::loadActionData(self::getAtPathActions($RouteConf->getActionPaths(), $RouteConf->getActions()));
            }catch(Exception $Exception){
                $urlListData    = [];
                unset($Exception);
            }
            if($RouteConf->isWebSocket()){
                $urlListGroupSocks[$groupName]    = $urlListData;
            }else{
                $urlListGroupWebs[$groupName]     = $urlListData;
            }
        }
        return [$urlListGroupWebs, $urlListGroupSocks];
    }

    /**
     * @param array $actionClassNameList
     * @return array
     * @throws Exception
     * loadActionData
     */
    private static function loadActionData(array $actionClassNameList):array
    {
        $urlListData                            = [];
        if(count($actionClassNameList) < 1) return $urlListData;
        foreach ($actionClassNameList as $actionClassName){
            if(!class_exists($actionClassName)) throw new Exception('Action ClassName Not Found, ['.$actionClassName.']');
            if(!((new $actionClassName()) instanceof ActionAbstract)) throw new Exception('Action ClassName Not Instanceof ActionAbstract, ['.$actionClassName.']');
            $ReqClassNameReflectionClass        = new ReflectionClass($actionClassName);
            $ReflectionMethodList               = $ReqClassNameReflectionClass->getMethods(ReflectionMethod::IS_PUBLIC);
            if(count($ReflectionMethodList) < 1) continue;
            foreach ($ReflectionMethodList as $ReflectionMethod){
                if(!($ReflectionMethod instanceof ReflectionMethod) || $ReflectionMethod->isStatic() || !$ReflectionMethod->isPublic()) continue;
                $commentSrc             = $ReflectionMethod->getDocComment();
                $comment                = '';
                if(preg_match('/\*\s+([^@]+)\n/i', $commentSrc, $match)) $comment = trim($match[1]);
                if(!preg_match_all('/@(auth|code|url|req|rsp|flag|err|limit)[ \t]*([\S ]*)\n/i', $commentSrc, $matchList)) continue;
                $flagList               = [];
                $urlList                = [];
                $authStr                = '';
                $codeStr                = '';
                $DTOClassName           = '';
                $VOClassName            = '';
                $ErrVOClassNameList     = [];
                $limitList              = [];
                // 自动提取错误码
                $codeList               = [];
                $codeStartLine          = $ReflectionMethod->getStartLine() - 1;
                $codeEndLine            = $ReflectionMethod->getEndLine();
                if($codeStartLine > 0 && $codeEndLine > 0 && $codeEndLine > $codeStartLine){
                    $codeFilePath       = Boot::getClassFilePath($actionClassName);
                    $codeContent        = '';
                    if(CommFile::getContentLine($codeFilePath, $codeContent, ($codeEndLine - $codeStartLine), $codeStartLine)){
                        if(preg_match_all('/ErrCodeEnum::(\w+)/', $codeContent, $matchCodeList)) foreach(array_unique($matchCodeList[1]) as $codeName){
                            if(!ErrCodeEnum::nameExists($codeName)) throw new Exception('ErrCodeName ['.$codeName.'] Not Found, ['.$actionClassName.']');
                            $codeNum    = ErrCodeEnum::getValue($codeName);
                            if($codeNum < 200000) continue;
                            $codeList[] = $codeNum;
                        }
                    }
                }
                foreach ($matchList[1] as $matchIndex => $matchVal){
                    $valStr             = trim($matchList[2][$matchIndex]??'');
                    $matchKey           = strtolower($matchVal);
                    switch($matchKey){
                        case 'flag':{
                            $flagVal    = $valStr;
                            if(strlen($flagVal) > 0) foreach (explode(',', $flagVal) as $flagValItem){
                                $flagValItem = trim($flagValItem);
                                if(strlen($flagValItem) > 0) $flagList[] = $flagValItem;
                            }
                            break;
                        }
                        case 'url':{
                            $urlPath    = $valStr;
                            if(strlen($urlPath) > 0) $urlList[]  = $urlPath;
                            break;
                        }
                        case 'auth':{
                            $authStr    = $valStr;
                            break;
                        }
                        case 'code':{   // 校验并格式化
                            if(strlen($valStr) > 0) foreach(explode(',', $valStr) as $codeName){
                                if(!ErrCodeEnum::nameExists($codeName)) throw new Exception('ErrCodeName ['.$codeName.'] Not Found, ['.$actionClassName.']');
                                $codeList[]     = ErrCodeEnum::getValue($codeName);
                            }
                            break;
                        }
                        case 'req':{
                            if(strlen($valStr) > 0){
                                $valStr         = preg_replace('/\./', '\\', $valStr);
                                $DTOClassName   = $valStr;
                            }
                            break;
                        }
                        case 'rsp':{
                            if(strlen($valStr) > 0){
                                $valStr         = preg_replace('/\./', '\\', $valStr);
                                $VOClassName    = $valStr;
                            }
                            break;
                        }
                        case 'err':{
                            if(strlen($valStr) > 0){
                                $valStr                 = preg_replace('/\./', '\\', $valStr);
                                $ErrVOClassNameList[]   = $valStr;
                            }
                            break;
                        }
                        case 'limit':{
                            if(strlen($valStr) > 0){
                                if(preg_match('/^([A-Z_\d]+)(\([^)]*\))?(\$?):(.*)/', $valStr, $matchLimit)){
                                    $limitConf          = [];
                                    if(CheckLimit::parseConf($matchLimit[1], $matchLimit[2], $matchLimit[3], $matchLimit[4], $valStr, $limitConf)){
                                        $limitList[]    = $limitConf;
                                    }else{
                                        throw new Exception($matchKey.' Limit ParseConf Error, ['.$actionClassName.'->'.$ReflectionMethod->getName().'], '.$valStr);
                                    }
                                }else{
                                    throw new Exception($matchKey.' Limit Conf Error, ['.$actionClassName.'->'.$ReflectionMethod->getName().'], '.$valStr);
                                }
                            }
                            break;
                        }
                    }
                }
                if(in_array('skip', $flagList, true) || in_array('SKIP', $flagList, true)) continue;    // 跳过
                if(count($codeList) > 0){
                    $codeList   = array_unique($codeList);
                    sort($codeList);
                    $codeStr    = implode(',', $codeList);
                }
                $urlList                        = array_unique($urlList);
                if(count($urlList) > 0){
                    foreach ($urlList as $urlPath) $urlListData[$urlPath] = [$actionClassName, $ReflectionMethod->getName(), $authStr, $codeStr, $DTOClassName, $VOClassName, $comment, $flagList, $ErrVOClassNameList, $limitList];
                }
            }
        }
        return $urlListData;
    }
}